#pragma once
#include <iostream>
#include <mutex>
#include <iostream>
#include <cassert>
#include <functional>
#include <algorithm>
#include <jsoncpp/json/json.h>
#include "oj_model2.hpp"
#include "oj_view.hpp"
#include "../comm/Logger.hpp"
#include "../comm/Util.hpp"
#include "../comm/httplib.h"
#include "../comm/Request.hpp"
#include "../comm/Response.hpp"
namespace ns_control
{
    using namespace ns_model;
    using namespace ns_view;
    using namespace ns_log;
    using namespace httplib;
    // 先描述
    class Machine
    {
    public:
        Machine() : _ip("none"), _port(-1), _load(0), _mutex(nullptr)
        {
        }

        void SetIp(const std::string &ip) { _ip = ip; }
        std::string GetIp() { return _ip; }
        void SetPort(uint16_t port) { _port = port; }
        uint16_t GetPort() { return _port; }
        void SetLoad(uint64_t load) { _load = load; }
        uint64_t GetLoad() { return _load; }
        void SetMuetx(std::mutex *mutex) { _mutex = mutex; }

        void InLoad()
        {
            if (_mutex)
                _mutex->lock();
            _load++;
            if (_mutex)
                _mutex->unlock();
        }
        void DeLoad()
        {
            if (_mutex)
                _mutex->lock();
            _load--;
            if (_mutex)
                _mutex->unlock();
        }

        void InitLoad()
        {
             if (_mutex)
                _mutex->lock();
            _load = 0;
            if (_mutex)
                _mutex->unlock();
        }
        ~Machine() {}

    private:
        std::string _ip;
        uint16_t _port;
        uint32_t _load; // 负载情况
        std::mutex *_mutex;
    };

    // 负载均衡模块
    const static std::string confpath = "./conf/server_mathine.conf";
    class LoadBlance
    {
        bool Load(const std::string &path)
        {
            std::ifstream in(path);
            if (!in.is_open())
            {
                LOG(LogLevel::DEBUG) << "load: " << confpath << " fail";
                return false;
            }

            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> socket;
                BoostUtil::CuttingSubstrings(&socket, line, ": ");
                if (socket.size() != 2)
                {
                    LOG(LogLevel::WARNING) << socket[1] << "切分失败";
                    continue;
                }
                std::string ip = socket[0];
                uint16_t port = std::atoi(socket[1].c_str());
                Machine machine;
                machine.SetIp(ip);
                machine.SetPort(port);
                machine.SetLoad(0);
                machine.SetMuetx(new std::mutex());

                _online_machines.emplace_back(_machines.size());
                _machines.emplace_back(machine);
            }
            return true;
        }

    public:
        LoadBlance()
        {
            assert(Load(confpath));
            LOG(LogLevel::DEBUG) << "加载: " << confpath << " 成功";
        }
#define debug
#ifdef debug
        void ShowOnlineID()
        {
            for (auto &ID : _online_machines)
            {
                std::cout << "id: " << ID << std::endl;
            }
        }
#endif
        void ShowId()
        {
            _mutex.lock();
            std::cout << "在线主机id: ";
            for (auto &e : _online_machines)
            {
                std::cout << e << " ";
            }

            std::cout << std::endl;

            std::cout << "离线主机id: ";
            for (auto &e : _offline_machines)
            {
                std::cout << e << " ";
            }
            std::cout << std::endl;
            _mutex.unlock();
        }

        bool SmartChoice(int *id, Machine **m)
        {
            if (id == nullptr || m == nullptr)
                return false;

            _mutex.lock();
            int size = _online_machines.size();
            if (size == 0)
            {
                _mutex.unlock();
                LOG(LogLevel::FATAL) << "所有后台编译主机都挂掉了，请运维赶紧检查";
                return false;
            }

            *id = _online_machines[0]; // 选择第一台主机,注意不是id = 0;
            *m = &_machines[_online_machines[0]];
            uint64_t load_min = _machines[_online_machines[0]].GetLoad();
            for (int i = 1; i < size; i++)
            {
                uint64_t load = _machines[_online_machines[i]].GetLoad();
                if (load_min > load)
                {
                    load_min = load;
                    *id = _online_machines[i];
                    *m = &_machines[_online_machines[i]];
                }
            }
            _mutex.unlock();
            return true;
        }

        void OffMachine(int which)
        {
            _mutex.lock();
            for (auto iter = _online_machines.begin(); iter != _online_machines.end(); iter++)
            {
                if (*iter == which)
                {
                    _machines[which].InitLoad(); // bug , 在加入offline时，将负载清0，避免寄存器保存，load没有清0
                    _online_machines.erase(iter);
                    _offline_machines.push_back(which);
                    break;
                }
            }
            _mutex.unlock();
        }

        void OnlineMachine()
        {
            // 规定一下，当所有主机全部离线时，统一上线。
            // 优雅的写法~
            _online_machines.insert(_online_machines.end(), _offline_machines.begin(), _offline_machines.end());
            _offline_machines.erase(_offline_machines.begin(), _offline_machines.end());

            LOG(LogLevel::INFO) << "所有主机全部上线了~";
        }
        ~LoadBlance() {}

    private:
        std::vector<Machine> _machines;     // 负载均衡的机器
        std::vector<int> _online_machines;  // 用下标管理在线的机器
        std::vector<int> _offline_machines; // 用下标管理离线的机器
        std::mutex _mutex;                  // 保护 _mathine
    };

    class Control
    {
    public:
        Control()
        {
        }
        ~Control() {}

    public:
        void RecoverMachine()
        {
            _load_blance.OnlineMachine();
        }
        // 将allquestions的网页转换为string
        bool AllQuestions(std::string *out)
        {
            if (out == nullptr)
                return false;

            std::vector<Question> all;
            if (!_model.GetAllQuestions(&all))
            {
                LOG(LogLevel::WARNING) << "获取所有题目失败";
                return false;
            }
            // 对获取上来的vector题目编号进行排序,从小到大
            sort(all.begin(), all.end(), [](const Question& q1, const Question& q2){
                return stoi(q1.number) < stoi(q2.number);
            });
            _view.ViewAllQuestions(all, out);
            return true;
        }

        // 将指定题目的网页转换为string
        bool OneQuestion(const std::string &number, std::string *out)
        {
            if (out == nullptr || number.empty())
                return false;

            Question q;
            if (!_model.GetOneQuestion(number, &q))
            {
                LOG(LogLevel::WARNING) << "获取指定: " << number << " 题目失败";
                return false;
            }
            _view.ViewOneQuestion(q, out);
            return true;
        }

        // code: #include<>...
        // input: ...
        void Judge(const std::string &number, const std::string &in_json, std::string *out_json)
        {
            // 0.获取题目信息
            Question q;
            _model.GetOneQuestion(number, &q);
            // 1. 构建请求
            ns_request::Request requset;
           
            requset.DeSerialize(in_json);
            // 拼接上题目的属性
            requset.cpu_limit = q.cpu_limit;
            requset.mem_limit = q.mem_limit;
            requset.code += "\n";
            requset.code += q.tail;
           
            std::string compile_string = requset.Serialize();
            // 3. 选择负载最低的主机
            std::string result;
            while (true)
            {
                int id = 0;
                Machine *m;
                if (!_load_blance.SmartChoice(&id, &m)) // 这说明所有主机都挂掉了
                {
                    break; // 所有主机挂掉了。里面有日志信息
                }

                // 4.构造客户端，访问编译服务器
                Client client(m->GetIp(), m->GetPort());
                m->InLoad(); // 增加负载
                auto ret = client.Post("/compile_run", compile_string, "application=json; charset=utf-8"); // 这样实现解耦
                if (ret)
                {
                    LOG(LogLevel::INFO) << "选择主机,id[" << id << "] ip[" << m->GetIp() << "] port[" \
                    << m->GetPort() << "] success, 当前主机负载: " << m->GetLoad();
                    if (ret->status == 200)
                    {
                        // 请求成功,并且访问服务器正常
                        m->DeLoad();
                        *out_json = ret->body;
                        LOG(LogLevel::INFO) << "请求编译和运行服务成功..." << "\n";
                        break;
                    }
                    m->DeLoad();
                }
                else
                {
                    // 请求失败,当前主机挂了
                    LOG(LogLevel::ERROR) << " 当前请求的主机id: " << id << " 详情: " \
                    << m->GetIp() << ":" << m->GetPort() << " 主机负载: " << m->GetLoad() << " 可能已经离线,后台继续负载均衡选择编译主机" << "\n";
                    
                    m->DeLoad();
                    _load_blance.OffMachine(id);
                    _load_blance.ShowId(); // for debug
                }
            }
        }

    private:
        Model _model;
        View _view;
        LoadBlance _load_blance;
    };
}